#include <crt_cond.h>
#include <crt_error.h>
#include <crt_sem.h>
#include <crt_mutex.h>
#include <crt_atomic.h>

#if (TARGET_OS == OS_WINDOWS)

int _cond_init(_cond_t* cond, int relative) {
	
	_sem_init(&cond->wait_sem, 0);
	_sem_init(&cond->wait_done, 0);
	_mutex_init(&cond->lock);

    cond->nsignal = 0;
    cond->waiting = 0;

	return 0;
}

void _cond_destroy(_cond_t* cond) {

	if (cond == NULL)
		return;
	
	_sem_destroy(&cond->wait_sem);
	_sem_destroy(&cond->wait_done);
	_mutex_destroy(&cond->lock);

    cond->nsignal = 0;
    cond->waiting = 0;

}

void _cond_signal(_cond_t* cond) {

	_mutex_lock(&cond->lock);
	if (cond->waiting > cond->nsignal) {


        cond->nsignal++;;

		_sem_post(&cond->wait_sem);
		_mutex_unlock(&cond->lock);
		_sem_wait(&cond->wait_done);
	}
	else {
		_mutex_unlock(&cond->lock);
	}
		

}

void _cond_broadcast(_cond_t* cond) {
	
	_mutex_lock(&cond->lock);
	if (cond->waiting > cond->nsignal) {
		int i = 0;
		int num = 0;

		num = (cond->waiting - cond->nsignal);

		cond->nsignal = cond->waiting;

		for (i = 0; i < num; ++i)
			_sem_post(&cond->wait_sem);

		_mutex_unlock(&cond->lock);

		for (i = 0; i < num; ++i)
			_sem_wait(&cond->wait_done);
	}
	else {
		_mutex_unlock(&cond->lock);
	}

}

void _cond_wait(_cond_t* cond, _mutex_t* mutex) {
	
	_cond_timedwait(cond, mutex, INFINITE);
}

int _cond_timedwait(_cond_t* cond, _mutex_t* mutex, unsigned long ms) {

	unsigned long result = 0;
	int last_waiter;

	HANDLE handles[2] = {
		cond->wait_sem,
		cond->wait_done
	};

	_mutex_unlock(mutex);

	_mutex_lock(&cond->lock);
	cond->waiting++;
	_mutex_unlock(&cond->lock);

	result = WaitForMultipleObjects(2, handles, FALSE, ms);

	_mutex_lock(&cond->lock);
	cond->waiting--;
	_mutex_unlock(&cond->lock);


	last_waiter = (result == WAIT_OBJECT_0 + 1) && (cond->waiting == 0);

	if (cond->nsignal > 0) {
	
		_sem_post(&cond->wait_done);

		_mutex_lock(&cond->lock);
		cond->nsignal--;
		_mutex_unlock(&cond->lock);
	}

	_mutex_lock(mutex);

	if (result == WAIT_OBJECT_0 || result == WAIT_OBJECT_0 + 1)
		return S_SUCCESS;

	if (result == WAIT_TIMEOUT)
		return S_TIMEOUT;

	return S_ERROR;
}

#elif (TARGET_OS == OS_POSIX)

int _cond_init(_cond_t* cond, int relative) {

	cond->relative = relative;

	pthread_mutex_init(&cond->mutex, NULL);

	if (relative) {
		int ret = 0;
		ret = pthread_condattr_init(&cond->attr);
		ret = pthread_condattr_setclock(&cond->attr, CLOCK_MONOTONIC);
		return pthread_cond_init(&cond->c, &cond->attr);
	}
	else {
		return pthread_cond_init(&cond->c, NULL);
	}
	return S_SUCCESS;
}

void _cond_destroy(_cond_t* cond) {

	if (cond->relative)
	{
		pthread_condattr_destroy(&(cond->attr));
	}

	pthread_mutex_destroy(&cond->mutex);

	if (pthread_cond_destroy(&cond->c))
		abort();
}

void _cond_signal(_cond_t* cond) {

	if (pthread_cond_signal(&cond->c))
		abort();
}

void _cond_broadcast(_cond_t* cond) {

	if (pthread_cond_broadcast(&cond->c))
		abort();
    
}

void _cond_wait(_cond_t* cond, _mutex_t* mutex) {

	if (pthread_cond_wait(&cond->c, mutex))
		abort();

}

int	_cond_timedwait(_cond_t* cond, _mutex_t* mutex, unsigned long ms) {

	int r;
	int retval;
	
	struct timeval delta;
	struct timespec abstime;


	if (cond->relative) {
		clock_gettime(CLOCK_MONOTONIC, &abstime);
		abstime.tv_nsec += (ms % 1000) * 1000000;
		abstime.tv_sec += ms / 1000;
	}
	else {
		gettimeofday(&delta, NULL);
		abstime.tv_sec = delta.tv_sec + (ms / 1000);
		abstime.tv_nsec = (delta.tv_usec + (ms % 1000) * 1000) * 1000;
	}

	if (abstime.tv_nsec > 1000000000) {
		abstime.tv_sec += 1;
		abstime.tv_nsec -= 1000000000;
	}


	pthread_mutex_lock(&cond->mutex);


#if defined(__ANDROID_API__) && __ANDROID_API__ < 21
	r = pthread_cond_timedwait_monotonic_np(&cond->c, &cond->mutex, &abstime);
#else
	r = pthread_cond_timedwait(&cond->c, &cond->mutex, &abstime);
#endif

	pthread_mutex_unlock(&cond->mutex);

	if (r == 0)
		return S_SUCCESS;

	if (r == ETIMEDOUT)
		return S_TIMEOUT;

	abort();
	return S_ERROR;
}

#elif (TARGET_OS == OS_DARWIN)


int _cond_init(_cond_t* cond, int relative) {

    cond->relative = relative;

    pthread_mutex_init(&cond->mutex, NULL);

    if (relative) {
        return pthread_cond_init(&cond->c, NULL);
    }
    else {
        return pthread_cond_init(&cond->c, NULL);
    }
    return S_SUCCESS;
}

void _cond_destroy(_cond_t* cond) {

    if (cond->relative)
    {
        pthread_condattr_destroy(&(cond->attr));
    }

    pthread_mutex_destroy(&cond->mutex);

    if (pthread_cond_destroy(&cond->c))
        abort();
}

void _cond_signal(_cond_t* cond) {

    if (pthread_cond_signal(&cond->c))
        abort();
}

void _cond_broadcast(_cond_t* cond) {

    if (pthread_cond_broadcast(&cond->c))
        abort();
}

void _cond_wait(_cond_t* cond, _mutex_t* mutex) {

    if (pthread_cond_wait(&cond->c, mutex))
        abort();

}



int _cond_timedwait(_cond_t* cond, _mutex_t* mutex, unsigned long ms) {

    int r;

    struct timespec abstime;

    if (__builtin_available(macOS 10.12, *)) {
        if (__builtin_available(iOS 10.0, *)) {
            clock_gettime(_CLOCK_MONOTONIC, &abstime);
        } else {
            // Fallback on earlier versions
        }if (__builtin_available(iOS 10.0, *)) {
            clock_gettime(_CLOCK_MONOTONIC, &abstime);
        } else {
            // Fallback on earlier versions
        }
    } else {
        // Fallback on earlier versions
    }
   abstime.tv_nsec += (ms % 1000) * 1000000;
   abstime.tv_sec += ms / 1000;
  

    if (abstime.tv_nsec > 1000000000) {
        abstime.tv_sec += 1;
        abstime.tv_nsec -= 1000000000;
    }
    
    pthread_mutex_lock(&cond->mutex);

    r = pthread_cond_timedwait_relative_np(&cond->c, &cond->mutex, &abstime);

    pthread_mutex_unlock(&cond->mutex);

    if (r == 0)
        return S_SUCCESS;

    if (r == ETIMEDOUT)
        return S_TIMEOUT;

    abort();
    return S_ERROR;
}

#endif


